package org.quipu.client;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.DisclosurePanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HTMLTable;
import static com.google.gwt.user.client.ui.HasHorizontalAlignment.ALIGN_LEFT;
import static com.google.gwt.user.client.ui.HasHorizontalAlignment.ALIGN_CENTER;
import static com.google.gwt.user.client.ui.HasHorizontalAlignment.ALIGN_RIGHT;
import static com.google.gwt.user.client.ui.HasVerticalAlignment.ALIGN_MIDDLE;
import static com.google.gwt.user.client.ui.HasVerticalAlignment.ALIGN_TOP;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.datepicker.client.DateBox;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.quipu.client.charts.BalanceChart;
import org.quipu.client.events.AccountChangedEvent;
import org.quipu.client.events.AccountSelectionChangedEvent;
import org.quipu.client.events.AppEvent;
import org.quipu.client.events.AppEventBus;
import org.quipu.client.events.AppEventListener;
import org.quipu.client.events.BookSelectionChangedEvent;
import org.quipu.client.events.LoginEvent;
import org.quipu.client.events.TransactionChangedEvent;
import org.quipu.dto.AccountDetailDto;
import org.quipu.dto.AccountDto;
import org.quipu.dto.CurrencyDto;

public class AccountsPanel extends AppEventWidget implements AppEventListener {

    private final DateTimeFormat dtf;
    private DisclosurePanel accountsPanel;
    private FlexTable accountsTable;
    private final Map<Integer, AccountDto> accountsRowCache;
    private final Map<Integer, AccountDto> debtsRowCache;
    private Collection<AccountDetailDto> accountsCache;
    private Long selectedAccountId;
    private Long selectedDebtId;
    private CheckBox plannedCheckBox;
    private DateBox onDateBox;
    private DisclosurePanel debtsPanel;
    private FlexTable debtsTable;
    private final BalanceChart balanceChart;
    private final HorizontalPanel hPanel;
    private final VerticalPanel editPanel;
    private final AccountEditPanel accountEditPanel;
    private final VerticalPanel vPanel;

    public AccountsPanel(AppEventBus eventBus, AppCache cache) {
        super(eventBus, cache);
        dtf = new DateTimeFormat("dd.MM.yyyy");
        hPanel = new HorizontalPanel();
        hPanel.addStyleName("contentPanel");
        hPanel.addStyleName("gridPanel");
        hPanel.setHorizontalAlignment(ALIGN_LEFT);
        hPanel.setVerticalAlignment(ALIGN_TOP);

        editPanel = new VerticalPanel();
        editPanel.addStyleName("gridPanel");
        editPanel.setHorizontalAlignment(ALIGN_LEFT);
        editPanel.setVerticalAlignment(ALIGN_TOP);
        editPanel.setSize("100%", "100%");
        accountEditPanel = new AccountEditPanel(eventBus, cache);
        editPanel.add(accountEditPanel.getWidget());
        hPanel.add(editPanel);

        vPanel = new VerticalPanel();
        vPanel.setHorizontalAlignment(ALIGN_LEFT);
        vPanel.setVerticalAlignment(ALIGN_TOP);
        vPanel.addStyleName("gridPanel");
        vPanel.setSize("100%", "100%");
        hPanel.add(vPanel);

        selectedAccountId = null;
        accountsRowCache = new HashMap<Integer, AccountDto>();
        accountsTable = new FlexTable();
        accountsTable.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                HTMLTable.Cell cell = accountsTable.getCellForEvent(event);
                selectAccount(cell.getRowIndex());
            }
        });
        accountsTable.addStyleName("dataTable");
        onDateBox = new DateBox();
        onDateBox.addStyleName("dateInput");
        onDateBox.setFormat(new DateBox.DefaultFormat(new DateTimeFormat("dd.MM.yyyy")));
        onDateBox.setValue(new Date());
        onDateBox.addValueChangeHandler(new ValueChangeHandler<Date>() {
            @Override
            public void onValueChange(ValueChangeEvent<Date> event) {
                updateAccounts();
            }
        });
        plannedCheckBox = new CheckBox("учитывать запланированные транзакции");
        plannedCheckBox.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                updateAccounts();
            }
        });
        plannedCheckBox.setValue(false);
        HorizontalPanel headPanel = new HorizontalPanel();
        headPanel.add(new Label("На дату:"));
        headPanel.add(onDateBox);
        headPanel.add(plannedCheckBox);
        headPanel.setHorizontalAlignment(ALIGN_LEFT);
        headPanel.setVerticalAlignment(ALIGN_MIDDLE);
        headPanel.addStyleName("gridPanel");
        vPanel.add(headPanel);
        accountsPanel = new DisclosurePanel("Счета");
        accountsPanel.getHeader().setStyleName("accountDisclosureHeader");
        accountsPanel.setOpen(true);
        accountsPanel.add(accountsTable);
        vPanel.add(accountsPanel);

        selectedDebtId = null;
        debtsRowCache = new HashMap<Integer, AccountDto>();
        debtsTable = new FlexTable();
        debtsTable.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                HTMLTable.Cell cell = debtsTable.getCellForEvent(event);
                selectDebt(cell.getRowIndex());
            }
        });
        debtsTable.addStyleName("dataTable");
        debtsPanel = new DisclosurePanel("Долги");
        debtsPanel.getHeader().setStyleName("accountDisclosureHeader");
        debtsPanel.setOpen(true);
        debtsPanel.add(debtsTable);
        vPanel.add(debtsPanel);

        balanceChart = new BalanceChart(eventBus, cache);
        vPanel.add(balanceChart.getWidget());
//        debtsPanel.add(approvedOnlyDebtsBox);
    }

    @Override
    public Widget getWidget() {
        return hPanel;
    }

    @Override
    public void init() {
        accountEditPanel.init();
        balanceChart.init();
        getEventBus().addListener(BookSelectionChangedEvent.class, this);
        getEventBus().addListener(LoginEvent.class, this);
        getEventBus().addListener(AccountChangedEvent.class, this);
        getEventBus().addListener(TransactionChangedEvent.class, this);
        updateAccounts();
    }

    private void updateAccounts() {
        if(getCache().getCurrentBook() != null) {
            Date onDate = onDateBox.getValue();
            if(onDate == null) {
                onDate = new Date();
                onDateBox.setValue(onDate);
            }
            getMainService().getBookAccountsCalculation(getCache().getCurrentBook().getId(), onDate, !plannedCheckBox.getValue(), new AsyncCallback<List<AccountDetailDto>>() {
                public void onSuccess(List<AccountDetailDto> accounts) {
                    accountsCache = accounts;
                    repaintAccountsTable();
                    repaintDebtsTable();
                }

                public void onFailure(Throwable caught) {
                    fireError(caught.getMessage());
                }
            });
        }
    }

    private void repaintAccountsTable() {
        accountsTable.removeAllRows();
        List<AccountDetailDto> ownAccounts = new LinkedList<AccountDetailDto>();
        Set<Long> ownCurrencies = new HashSet<Long>();
        Map<Long, Double> initBalances = new HashMap<Long, Double>();
        Map<Long, Double> balances = new HashMap<Long, Double>();
        for(AccountDetailDto accountCalcs : accountsCache) {
            if(!accountCalcs.getAccount().isDebt()) {
                ownAccounts.add(accountCalcs);
                ownCurrencies.addAll(accountCalcs.getAccount().getInitialBallances().keySet());
                ownCurrencies.addAll(accountCalcs.getBalance().keySet());
            }
        }
        Collections.sort(ownAccounts, new Comparator<AccountDetailDto>() {
            @Override
            public int compare(AccountDetailDto o1, AccountDetailDto o2) {
                return o1.getAccount().getTitle().compareTo(o2.getAccount().getTitle());
            }
        });
        FlexTable.FlexCellFormatter cellFormatter = accountsTable.getFlexCellFormatter();
        final FlexTable.RowFormatter rowFormatter = accountsTable.getRowFormatter();
        int row = 0;
        accountsTable.setWidget(row, 0, new Label("Счет"));
        cellFormatter.addStyleName(row, 0, "tableCell");
        accountsTable.setWidget(row, 1, new Label("Создан"));
        cellFormatter.addStyleName(row, 1, "tableCell");
        final int curCount = ownCurrencies.size();
        CurrencyDto cur;
        if(curCount > 0) {
            cellFormatter.setRowSpan(row, 0, 2);
            cellFormatter.setRowSpan(row, 1, 2);
            accountsTable.setWidget(row, 2, new Label("Начальный баланс"));
            accountsTable.setWidget(row, 3, new Label("Баланс"));
            if(curCount > 1) {
                cellFormatter.setColSpan(row, 2, curCount);
                cellFormatter.setColSpan(row, 3, curCount);
            }
            cellFormatter.addStyleName(row, 2, "tableCell");
            cellFormatter.addStyleName(row, 3, "tableCell");
            int col = 0;
            row++;
            for(Long curId: ownCurrencies)  {
                cur = getCache().getCurrency(curId);
                if(cur != null) {
                    accountsTable.setWidget(row, col, new Label(cur.getTitle()));
                    accountsTable.setWidget(row, col + curCount, new Label(cur.getTitle()));
                }
                cellFormatter.addStyleName(row, col + curCount, "tableCell");
                cellFormatter.addStyleName(row, col++, "tableCell");
                initBalances.put(curId, 0.0);
                balances.put(curId, 0.0);
            }
            rowFormatter.addStyleName(row, "tableHeader");
        }
        row++;
        rowFormatter.addStyleName(0, "tableHeader");
        Double sum, initSum;
        int col;
        accountsRowCache.clear();
        for(AccountDetailDto accountCalcs : ownAccounts) {
            col = 0;
            final AccountDto account = accountCalcs.getAccount();
            accountsRowCache.put(row, account);
            accountsTable.setWidget(row, col, new Label(account.getTitle()));
            cellFormatter.setAlignment(row, col, ALIGN_LEFT, ALIGN_MIDDLE);
            cellFormatter.addStyleName(row, col++, "tableCell");
            accountsTable.setWidget(row, col, new Label(dtf.format(account.getCreationDate())));
            cellFormatter.setAlignment(row, col, ALIGN_CENTER, ALIGN_MIDDLE);
            cellFormatter.addStyleName(row, col++, "tableCell");
            for(Long curId: ownCurrencies) {
                initSum = accountCalcs.getAccount().getInitialBallance(curId);
                if(initSum == null) {
                    initSum = 0.0;
                }
                sum = accountCalcs.getCurrencyBalance(curId);
                if(sum == null) {
                    sum = 0.0;
                }
                initBalances.put(curId, initBalances.get(curId) + initSum);
                balances.put(curId, balances.get(curId) + sum);
                accountsTable.setWidget(row, col, new Label(StringUtils.formatSum(initSum, true)));
                cellFormatter.setAlignment(row, col, ALIGN_RIGHT, ALIGN_MIDDLE);
                cellFormatter.addStyleName(row, col, "tableCell");
                accountsTable.setWidget(row, curCount + col, new Label(StringUtils.formatSum(sum, true)));
                cellFormatter.setAlignment(row, curCount + col, ALIGN_RIGHT, ALIGN_MIDDLE);
                cellFormatter.addStyleName(row, curCount + col++, "tableCell");
            }
            if(account.getId().equals(selectedAccountId)) {
                rowFormatter.addStyleName(row, "tableSelectedRow");
            }
            rowFormatter.addStyleName(row, "tableSelectableRow");
            row++;
        }
        col = 0;
        accountsTable.setWidget(row, col, new Label("ИТОГО"));
        cellFormatter.setColSpan(row, col, 2);
        cellFormatter.addStyleName(row, col++, "tableCell");
        for(Long curId: ownCurrencies) {
            accountsTable.setWidget(row, col, new Label(StringUtils.formatSum(initBalances.get(curId), true)));
            cellFormatter.setAlignment(row, col, ALIGN_RIGHT, ALIGN_MIDDLE);
            cellFormatter.addStyleName(row, col, "tableCell");
            accountsTable.setWidget(row, initBalances.size() + col, new Label(StringUtils.formatSum(balances.get(curId), true)));
            cellFormatter.setAlignment(row, initBalances.size() + col, ALIGN_RIGHT, ALIGN_MIDDLE);
            cellFormatter.addStyleName(row, initBalances.size() + col, "tableCell");
            col++;
        }
        rowFormatter.addStyleName(row, "tableTotal");
    }

    private void repaintDebtsTable() {
        debtsTable.removeAllRows();
        List<AccountDetailDto> debts = new LinkedList<AccountDetailDto>();
        Set<Long> debtsCurrencies = new HashSet<Long>();
        Map<Long, Double> balances = new HashMap<Long, Double>();
        for(AccountDetailDto debtCalcs : accountsCache) {
            if(debtCalcs.getAccount().isDebt()) {
                debts.add(debtCalcs);
                debtsCurrencies.addAll(debtCalcs.getBalance().keySet());
            }
        }
        Collections.sort(debts, new Comparator<AccountDetailDto>() {
            @Override
            public int compare(AccountDetailDto o1, AccountDetailDto o2) {
                return o1.getAccount().getTitle().compareTo(o2.getAccount().getTitle());
            }
        });
        FlexTable.FlexCellFormatter cellFormatter = debtsTable.getFlexCellFormatter();
        final FlexTable.RowFormatter rowFormatter = debtsTable.getRowFormatter();
        int row = 0;
        debtsTable.setWidget(row, 0, new Label("Счет"));
        cellFormatter.addStyleName(row, 0, "tableCell");
        debtsTable.setWidget(row, 1, new Label("Создан"));
        cellFormatter.addStyleName(row, 1, "tableCell");
        final int curCount = debtsCurrencies.size();
        CurrencyDto cur;
        if(curCount > 0) {
            cellFormatter.setRowSpan(row, 0, 2);
            cellFormatter.setRowSpan(row, 1, 2);
            debtsTable.setWidget(row, 2, new Label("Долг"));
            if(curCount > 1) {
                cellFormatter.setColSpan(row, 2, curCount);
            }
            cellFormatter.addStyleName(row, 2, "tableCell");
            int col = 0;
            row++;
            for(Long curId: debtsCurrencies)  {
                cur = getCache().getCurrency(curId);
                if(cur != null) {
                    debtsTable.setWidget(row, col, new Label(cur.getTitle()));
                }
                cellFormatter.addStyleName(row, col++, "tableCell");
                balances.put(curId, 0.0);
            }
            rowFormatter.addStyleName(row, "tableHeader");
        }
        row++;
        rowFormatter.addStyleName(0, "tableHeader");
        Double sum;
        int col;
        debtsRowCache.clear();
        for(AccountDetailDto debtCalcs : debts) {
            col = 0;
            final AccountDto debt = debtCalcs.getAccount();
            debtsRowCache.put(row, debt);
            debtsTable.setWidget(row, col, new Label(debt.getTitle()));
            cellFormatter.setAlignment(row, col, ALIGN_LEFT, ALIGN_MIDDLE);
            cellFormatter.addStyleName(row, col++, "tableCell");
            debtsTable.setWidget(row, col, new Label(dtf.format(debt.getCreationDate())));
            cellFormatter.setAlignment(row, col, ALIGN_CENTER, ALIGN_MIDDLE);
            cellFormatter.addStyleName(row, col++, "tableCell");
            for(Long curId: debtsCurrencies) {
                sum = debtCalcs.getCurrencyBalance(curId);
                if(sum == null) {
                    sum = 0.0;
                }
                balances.put(curId, balances.get(curId) + sum);
                debtsTable.setWidget(row, col, new Label(StringUtils.formatSum(sum, true)));
                cellFormatter.setAlignment(row, col, ALIGN_RIGHT, ALIGN_MIDDLE);
                cellFormatter.addStyleName(row, col++, "tableCell");
            }
            if(debt.getId().equals(selectedDebtId)) {
                rowFormatter.addStyleName(row, "tableSelectedRow");
            }
            rowFormatter.addStyleName(row, "tableSelectableRow");
            row++;
        }
        col = 0;
        debtsTable.setWidget(row, col, new Label("ИТОГО"));
        cellFormatter.setColSpan(row, col, 2);
        cellFormatter.addStyleName(row, col++, "tableCell");
        for(Long curId: debtsCurrencies) {
            debtsTable.setWidget(row, col, new Label(StringUtils.formatSum(balances.get(curId), true)));
            cellFormatter.setAlignment(row, col, ALIGN_RIGHT, ALIGN_MIDDLE);
            cellFormatter.addStyleName(row, col, "tableCell");
            col++;
        }
        rowFormatter.addStyleName(row, "tableTotal");
    }

    private void selectAccount(int row) {
        AccountDto account = accountsRowCache.get(row);
        if(account != null) {
            if(account.getId().equals(selectedAccountId)) {
                selectedAccountId = null;
                getEventBus().fireEvent(new AccountSelectionChangedEvent(this, null));
            } else {
                selectedAccountId = account.getId();
                selectedDebtId = null;
                getEventBus().fireEvent(new AccountSelectionChangedEvent(this, account));
            }
            repaintAccountsTable();
            repaintDebtsTable();
        }
    }

    private void selectDebt(int row) {
        AccountDto debt = debtsRowCache.get(row);
        if(debt != null) {
            if(debt.getId().equals(selectedDebtId)) {
                selectedDebtId = null;
                getEventBus().fireEvent(new AccountSelectionChangedEvent(this, null));
            } else {
                selectedDebtId = debt.getId();
                selectedAccountId = null;
                getEventBus().fireEvent(new AccountSelectionChangedEvent(this, debt));
            }
            repaintAccountsTable();
            repaintDebtsTable();
        }
    }

    public void handleEvent(AppEvent event) {
        if(event != null) {
            if(event instanceof LoginEvent) {
                selectBook();
            } else if(event instanceof BookSelectionChangedEvent) {
                selectBook();
            } else if(event instanceof AccountChangedEvent || event instanceof TransactionChangedEvent) {
                updateAccounts();
            }
        }
    }

    private void selectBook() {
        if(getCache().getCurrentBook() != null) {
            updateAccounts();
            show();
        } else {
            hide();
        }
    }

    private void hide() {
        hPanel.clear();
        hPanel.add(new Label("Выберите книгу"));
    }

    private void show() {
        hPanel.clear();
        hPanel.add(editPanel);
        hPanel.add(vPanel);
    }
}
